package com.bzkj.common.utils.poi;

import java.io.File;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.net.URLEncoder;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang3.ArrayUtils;
import org.apache.poi.util.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.itextpdf.text.BaseColor;
import com.itextpdf.text.Document;
import com.itextpdf.text.Element;
import com.itextpdf.text.Font;
import com.itextpdf.text.PageSize;
import com.itextpdf.text.Paragraph;
import com.itextpdf.text.Rectangle;
import com.itextpdf.text.pdf.BaseFont;
import com.itextpdf.text.pdf.PdfPCell;
import com.itextpdf.text.pdf.PdfPTable;
import com.itextpdf.text.pdf.PdfWriter;
import com.bzkj.common.annotation.Excel;
import com.bzkj.common.annotation.Excel.Type;
import com.bzkj.common.annotation.Excels;
import com.bzkj.common.config.RuoYiConfig;
import com.bzkj.common.core.domain.AjaxResult;
import com.bzkj.common.core.text.Convert;
import com.bzkj.common.exception.UtilException;
import com.bzkj.common.utils.DateUtils;
import com.bzkj.common.utils.DictUtils;
import com.bzkj.common.utils.StringUtils;

/**
 * @projectName: prt-wms
 * @package: com.bzkj.common.utils.poi
 * @className: ExcelPDFUtil
 * @description: Excel转PDF工具类
 * @author: C1309
 * @createDate: 2025年03月20日
 * @version: v1.0
 */
public class ExcelPDFUtil<T> {
    private static final Logger log = LoggerFactory.getLogger(ExcelPDFUtil.class);

    /**
     * 文件名称
     */
    private String pdfName;

    /**
     * 导出类型（EXPORT:导出数据；IMPORT：导入模板）
     */
    private Type type;

    /**
     * 文档信息
     */
    private Document doc;

    /**
     * 导入导出数据列表
     */
    private List<T> list;

    /**
     * 注解列表
     */
    private List<Object[]> fields;

    /**
     * 用于dictType属性数据存储，避免重复查缓存
     */
    public Map<String, String> sysDictMap = new ConcurrentHashMap<String, String>();

    /**
     * 标题
     */
    private String title;

    /**
     * 需要排除列属性
     */
    public String[] excludeFields;

    /**
     * 实体对象
     */
    public Class<T> clazz;

    /**
     * 导出人
     */
    private String exporter;

    /**
     * 导出时间
     */
    private String exportTime;

    /**
     * 导出日志范围
     */
    private String exportRange;

    public ExcelPDFUtil(Class<T> clazz) {
        this.clazz = clazz;
    }

    /**
     * 隐藏Excel中列属性
     *
     * @param fields 列属性名 示例[单个"name"/多个"id","name"]
     */
    public void hideColumn(String... fields) {
        this.excludeFields = fields;
    }

    /**
     * 初始化PDF导出工具类
     *
     * @param list        数据列表
     * @param pdfName     PDF文件名
     * @param title       标题
     * @param type        导出类型
     * @param exporter    导出人
     * @param exportTime  导出时间
     * @param exportRange 导出日志范围
     */
    public void init(List<T> list, String pdfName, String title, Type type, String exporter, String exportTime, String exportRange) {
        if (list == null) {
            list = new ArrayList<T>();
        }
        this.list = list;
        this.pdfName = pdfName;
        this.type = type;
        this.title = title;
        this.exporter = exporter;
        this.exportTime = exportTime;
        this.exportRange = exportRange;
        createExcelField();
    }

    /**
     * 创建PDF标题段落
     *
     * @return 标题段落对象
     * @throws Exception 字体创建异常
     */
    public Paragraph createTitle() throws Exception {
        if (StringUtils.isNotEmpty(title)) {
            // 创建中文字体
            BaseFont bfChinese = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);
            // 设置标题字体样式：字体大小为22，加粗
            Font firsetTitleFont = new Font(bfChinese, 22, Font.BOLD);
            // 创建段落对象，设置标题内容
            Paragraph paragraph = new Paragraph(title, firsetTitleFont);
            // 设置行间距为0
            paragraph.setLeading(0.0F);
            // 设置段落对齐方式为居中
            paragraph.setAlignment(Element.ALIGN_CENTER);
            // 设置首行缩进为0
            paragraph.setFirstLineIndent(0.0F);
            // 设置段落之间的空白为200f
            paragraph.setExtraParagraphSpace(200f);
            // 设置段落整体的左缩进为0
            paragraph.setIndentationLeft(0.0f);
            // 设置段落整体的右缩进为0
            paragraph.setIndentationRight(0.0f);
            // 设置段落下方的空白距离为10f
            paragraph.setSpacingAfter(10f);
            // 设置段落上方的留白为0，如果本页的第一个段落，该设置无效
            paragraph.setSpacingBefore(0.0f);
            return paragraph;
        } else {
            return null;
        }
    }

    /**
     * 对list数据源将其里面的数据导入到excel表单
     *
     * @param dataList  导出数据集合
     * @param sheetName 工作表的名称
     * @return 结果
     */
    public AjaxResult exportPDF(List<T> dataList, String sheetName) {
        return exportPDF(dataList, sheetName, StringUtils.EMPTY);
    }

    /**
     * 对list数据源将其里面的数据导入到excel表单
     *
     * @param dataList 导出数据集合
     * @param pdfName  工作表的名称
     * @param title    标题
     * @return 结果
     */
    public AjaxResult exportPDF(List<T> dataList, String pdfName, String title) {
        this.init(dataList, pdfName, title, Type.EXPORT, "", "", "");
        return writePdf(null);
    }

    /**
     * 对list数据源将其里面的数据导入到excel表单
     *
     * @param response 返回数据
     * @param dataList 导出数据集合
     * @param pdfName  文件名称
     * @return 结果
     */
    public AjaxResult exportPDF(HttpServletResponse response, List<T> dataList, String pdfName) {
        try {
            return exportPDF(response, dataList, pdfName, StringUtils.EMPTY);
        } catch (Exception e) {
            log.error("导出Excel异常{}", e.getMessage());
            throw new UtilException("导出PDF失败，请联系系统管理员！");
        }
    }

    /**
     * 对list数据源将其里面的数据导入到excel表单
     *
     * @param response 返回数据
     * @param dataList 导出数据集合
     * @param pdfName  文件的名称
     * @param title    标题
     * @return 结果
     */
    public AjaxResult exportPDF(HttpServletResponse response, List<T> dataList, String pdfName, String title)
            throws Exception {
        response.setContentType("application/pdf");
        response.setHeader("Expires", "0");
        response.setHeader("Cache-Control", "must-revalidate, post-check=0, pre-check=0");
        response.setHeader("Pragma", "public");
        response.setHeader("Content-disposition", "attachment; filename=".concat(String.valueOf(URLEncoder.encode(pdfName + ".pdf", "UTF-8"))));
        response.setCharacterEncoding("utf-8");
        this.init(dataList, pdfName, title, Type.EXPORT, "", "", "");
        return this.writePdf(response);
    }

    /**
     * 导出PDF文件
     *
     * @param dataList    数据列表
     * @param pdfName     PDF文件名
     * @param title       标题
     * @param exporter    导出人
     * @param exportTime  导出时间
     * @param exportRange 导出日志范围
     * @return 操作结果
     */
    public AjaxResult exportPDF(List<T> dataList, String pdfName, String title, String exporter, String exportTime, String exportRange) {
        this.init(dataList, pdfName, title, Type.EXPORT, exporter, exportTime, exportRange);
        return writePdf(null);
    }

    /**
     * 导出PDF文件到HTTP响应
     *
     * @param response    HTTP响应对象
     * @param dataList    数据列表
     * @param pdfName     PDF文件名
     * @param title       标题
     * @param exporter    导出人
     * @param exportTime  导出时间
     * @param exportRange 导出日志范围
     * @return 操作结果
     */
    public AjaxResult exportPDF(HttpServletResponse response, List<T> dataList, String pdfName, String title, String exporter, String exportTime, String exportRange) {
        try {
            response.setContentType("application/pdf");
            response.setHeader("Expires", "0");
            response.setHeader("Cache-Control", "must-revalidate, post-check=0, pre-check=0");
            response.setHeader("Pragma", "public");
            response.setHeader("Content-disposition", "attachment; filename=".concat(String.valueOf(URLEncoder.encode(pdfName + ".pdf", "UTF-8"))));
            response.setCharacterEncoding("utf-8");
            this.init(dataList, pdfName, title, Type.EXPORT, exporter, exportTime, exportRange);
            return this.writePdf(response);
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 将数据写入PDF文件
     *
     * @param response HTTP响应对象
     * @return 操作结果
     */
    public AjaxResult writePdf(HttpServletResponse response) {
        OutputStream out = null;
        String filename = "";
        try {
            if (response == null) {
                // 如果response为空，生成文件名并创建输出流
                filename = encodingFilename(pdfName);
                out = Files.newOutputStream(Paths.get(getAbsoluteFile(filename)));
            } else {
                // 否则使用response的输出流
                out = response.getOutputStream();
            }
            // 创建A4横版大小的矩形页面
            Rectangle rect = new Rectangle(PageSize.A4.rotate());
            doc = new Document(rect);
            // 将文档与输出流关联
            PdfWriter.getInstance(doc, out);
            doc.open();
            doc.newPage();
            // 将标题添加到文档上
            if (createTitle() != null) {
                doc.add(createTitle());
            }

            // 添加导出人、导出时间、导出日志范围信息
            if (StringUtils.isNotEmpty(exporter) || StringUtils.isNotEmpty(exportTime) || StringUtils.isNotEmpty(exportRange)) {
                // 创建中文字体
                BaseFont bfChinese = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);
                // 设置信息字体样式：字体大小为10，正常，黑色
                Font infoFont = new Font(bfChinese, 10, Font.NORMAL, BaseColor.BLACK);
                Paragraph infoParagraph = new Paragraph();
                StringBuilder infoBuilder = new StringBuilder();
                if (StringUtils.isNotEmpty(exporter)) {
                    infoBuilder.append("导出人: ").append(exporter).append("     ");
                }
                if (StringUtils.isNotEmpty(exportTime)) {
                    infoBuilder.append("导出时间: ").append(exportTime).append("     ");
                }
                if (StringUtils.isNotEmpty(exportRange)) {
                    infoBuilder.append("导出日志范围: ").append(exportRange);
                }
                infoParagraph.add(new Paragraph(infoBuilder.toString(), infoFont));
                // 设置信息与表格之间的间隙为10f
                infoParagraph.setSpacingAfter(10f);
                doc.add(infoParagraph);
            }

            // 添加表格，fields.size() 列
            PdfPTable table = new PdfPTable(fields.size());
            // 设置列宽
            int numberOfColumns = fields.size();
            int[] widths = new int[numberOfColumns];
            // 分配总宽度100，平均到每一列
            Arrays.fill(widths, 100 / numberOfColumns);
            table.setWidths(widths);
            // 设置表格宽度比例为100%
            table.setWidthPercentage(100);
            // 设置表格上面空白宽度为0
            table.setSpacingBefore(0f);
            // 设置表格下面空白宽度为0
            table.setSpacingAfter(0f);
            // 设置表格默认为无边框
            table.getDefaultCell().setBorder(0);

            // 创建中文字体
            BaseFont bfChinese = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);
            for (int i = 0; i < list.size(); i++) {
                if (i == 0) {
                    // 添加表头
                    for (Object[] os : fields) {
                        Excel excel = (Excel) os[1];
                        // 设置表头字体样式：字体大小为12，正常，白色
                        Font textFont = new Font(bfChinese, 12, Font.NORMAL, BaseColor.WHITE);
                        PdfPCell cell1 = new PdfPCell(new Paragraph(excel.name(), textFont));
                        // 设置背景颜色为灰色
                        cell1.setBackgroundColor(new BaseColor(128, 128, 128));
                        // 设置边框颜色为黑色
                        cell1.setBorderColor(BaseColor.BLACK);
                        // 设置内容水平居中显示
                        cell1.setHorizontalAlignment(Element.ALIGN_CENTER);
                        // 设置垂直居中
                        cell1.setVerticalAlignment(Element.ALIGN_MIDDLE);
                        table.addCell(cell1);
                    }
                }
                // 添加表格内容
                T vo = list.get(i);
                for (Object[] os : fields) {
                    Field field = (Field) os[0];
                    Excel excel = (Excel) os[1];
                    // 获取字段值
                    Object value = getTargetValue(vo, field, excel);
                    // 设置内容字体样式：字体大小为11，正常，黑色
                    Font textFont = new Font(bfChinese, 11, Font.NORMAL, BaseColor.BLACK);
                    String dateFormat = excel.dateFormat();
                    String readConverterExp = excel.readConverterExp();
                    String separator = excel.separator();
                    String dictType = excel.dictType();

                    // 根据字段类型进行格式化
                    if (StringUtils.isNotEmpty(dateFormat) && StringUtils.isNotNull(value)) {
                        value = parseDateToStr(dateFormat, value);
                    } else if (StringUtils.isNotEmpty(readConverterExp) && StringUtils.isNotNull(value)) {
                        value = convertByExp(Convert.toStr(value), readConverterExp, separator);
                    } else if (StringUtils.isNotEmpty(dictType) && StringUtils.isNotNull(value)) {
                        if (!sysDictMap.containsKey(dictType + value)) {
                            String lable = convertDictByExp(Convert.toStr(value), dictType, separator);
                            sysDictMap.put(dictType + value, lable);
                        }
                        value = sysDictMap.get(dictType + value);
                    } else if (value instanceof BigDecimal && -1 != excel.scale()) {
                        value = (((BigDecimal) value).setScale(excel.scale(), excel.roundingMode())).doubleValue();
                    } else {
                        String cellValue = Convert.toStr(value);
                        value = StringUtils.isNull(value) ? excel.defaultValue() : cellValue + excel.suffix();
                    }
                    PdfPCell cell11 = new PdfPCell(new Paragraph(value.toString(), textFont));
                    table.addCell(cell11);
                }
            }
            doc.add(table);
            return AjaxResult.success(filename);
        } catch (Exception e) {
            log.error("导出Excel异常{}", e.getMessage());
            throw new UtilException("导出PDF失败，请联系网站管理员！");
        } finally {
            doc.close();
            IOUtils.closeQuietly(out);
        }
    }

    /**
     * 解析字典值
     *
     * @param dictValue 字典值
     * @param dictType  字典类型
     * @param separator 分隔符
     * @return 字典标签
     */
    public static String convertDictByExp(String dictValue, String dictType, String separator) {
        return DictUtils.getDictLabel(dictType, dictValue, separator);
    }

    /**
     * 解析导出值 0=男,1=女,2=未知
     *
     * @param propertyValue 参数值
     * @param converterExp  翻译注解
     * @param separator     分隔符
     * @return 解析后值
     */
    public static String convertByExp(String propertyValue, String converterExp, String separator) {
        StringBuilder propertyString = new StringBuilder();
        String[] convertSource = converterExp.split(",");
        for (String item : convertSource) {
            String[] itemArray = item.split("=");
            if (StringUtils.containsAny(propertyValue, separator)) {
                for (String value : propertyValue.split(separator)) {
                    if (itemArray[0].equals(value)) {
                        propertyString.append(itemArray[1] + separator);
                        break;
                    }
                }
            } else {
                if (itemArray[0].equals(propertyValue)) {
                    return itemArray[1];
                }
            }
        }
        return StringUtils.stripEnd(propertyString.toString(), separator);
    }

    /**
     * 格式化不同类型的日期对象
     *
     * @param dateFormat 日期格式
     * @param val        被格式化的日期对象
     * @return 格式化后的日期字符
     */
    public String parseDateToStr(String dateFormat, Object val) {
        if (val == null) {
            return "";
        }
        String str;
        if (val instanceof Date) {
            str = DateUtils.parseDateToStr(dateFormat, (Date) val);
        } else if (val instanceof LocalDateTime) {
            str = DateUtils.parseDateToStr(dateFormat, DateUtils.toDate((LocalDateTime) val));
        } else if (val instanceof LocalDate) {
            str = DateUtils.parseDateToStr(dateFormat, DateUtils.toDate((LocalDate) val));
        } else {
            str = val.toString();
        }
        return str;
    }

    /**
     * 编码文件名
     */
    public String encodingFilename(String filename) {
        filename = UUID.randomUUID() + "_" + filename + ".pdf";
        return filename;
    }

    /**
     * 获取下载路径
     *
     * @param filename 文件名称
     */
    public String getAbsoluteFile(String filename) {
        String downloadPath = RuoYiConfig.getDownloadPath() + filename;
        File desc = new File(downloadPath);
        if (!desc.getParentFile().exists()) {
            desc.getParentFile().mkdirs();
        }
        return downloadPath;
    }

    /**
     * 获取bean中的属性值
     *
     * @param vo    实体对象
     * @param field 字段
     * @param excel 注解
     * @return 最终的属性值
     * @throws Exception
     */
    private Object getTargetValue(T vo, Field field, Excel excel) throws Exception {
        Object o = field.get(vo);
        if (StringUtils.isNotEmpty(excel.targetAttr())) {
            String target = excel.targetAttr();
            if (target.contains(".")) {
                String[] targets = target.split("[.]");
                for (String name : targets) {
                    o = getValue(o, name);
                }
            } else {
                o = getValue(o, target);
            }
        }
        return o;
    }

    /**
     * 以类的属性的get方法方法形式获取值
     *
     * @param o
     * @param name
     * @return value
     * @throws Exception
     */
    private Object getValue(Object o, String name) throws Exception {
        if (StringUtils.isNotNull(o) && StringUtils.isNotEmpty(name)) {
            Class<?> clazz = o.getClass();
            Field field = clazz.getDeclaredField(name);
            field.setAccessible(true);
            o = field.get(o);
        }
        return o;
    }

    /**
     * 得到所有定义字段
     */
    private void createExcelField() {
        this.fields = getFields();
        this.fields = this.fields.stream().sorted(Comparator.comparing(objects -> ((Excel) objects[1]).sort())).collect(Collectors.toList());
    }

    /**
     * 获取字段注解信息
     */
    public List<Object[]> getFields() {
        List<Object[]> fields = new ArrayList<Object[]>();
        List<Field> tempFields = new ArrayList<>();
        tempFields.addAll(Arrays.asList(clazz.getSuperclass().getDeclaredFields()));
        tempFields.addAll(Arrays.asList(clazz.getDeclaredFields()));
        for (Field field : tempFields) {
            if (!ArrayUtils.contains(this.excludeFields, field.getName())) {
                // 单注解
                if (field.isAnnotationPresent(Excel.class)) {
                    Excel attr = field.getAnnotation(Excel.class);
                    if (attr != null && (attr.type() == Type.ALL || attr.type() == type)) {
                        field.setAccessible(true);
                        fields.add(new Object[]{field, attr});
                    }
                }

                // 多注解
                if (field.isAnnotationPresent(Excels.class)) {
                    Excels attrs = field.getAnnotation(Excels.class);
                    Excel[] excels = attrs.value();
                    for (Excel attr : excels) {
                        if (!ArrayUtils.contains(this.excludeFields, field.getName() + "." + attr.targetAttr())
                                && (attr != null && (attr.type() == Type.ALL || attr.type() == type))) {
                            field.setAccessible(true);
                            fields.add(new Object[]{field, attr});
                        }
                    }
                }
            }
        }
        return fields;
    }
}
